/*
 * Copyright 2010 - 2011, The PLDesktop Development Team
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

#include "machine.h"

#include <machine/pc/processor/processor.h>
#include <bootstrapinfo.h>
#include <machine/pc/memory/physicalpagemanager.h>
#include <machine/pc/memory/virtualpagemanager.h>
#include <machine/pc/memory/memmanager.h>
#include <kerneldebugconsole.h>
#include <bootoutput.h>
#include <crtload.h>

#include <PLExceptionBase.h>
#include <exception/PLException.h>
using namespace PLSDK;

PLMachine::PLMachine() : m_processorCount(1) {
}

void PLMachine::setMultiboot(unsigned long address) {
	m_bootInfo = (Multiboot_info *)address;
}

void PLMachine::readMultibootInformtion() {
	const char *types[6] = { "", "Free Memory", "Reserved Memory", "ACPI Reclaimable", "ACPI NVS", "Bad Memory" };
	m_commandline = new PLCommandline((char *)m_bootInfo->cmdline);
	PLKernelDebugConsole &output = PLKernelDebugConsole::instance();
	output.printf("System memory information:\n");
	output.printf("Lower memory: %d byte (%d Kb)\n", (m_bootInfo->mem_lower * 1024), m_bootInfo->mem_lower);
	output.printf("Upper memory: %d byte (%d Kb / %d Mb / %d Gb)\n",
					  (m_bootInfo->mem_upper * 1024),
					  m_bootInfo->mem_upper,
					  (int)(((double)m_bootInfo->mem_upper / 1024) + 0.5),
					  (int)(((double)m_bootInfo->mem_upper / 1024 / 1024) + 0.5));
	output.printf("Total memory: %d byte (%d Kb / %d Mb / %d Gb)\n",
					  ((m_bootInfo->mem_upper + 1024) * 1024),
					  (m_bootInfo->mem_upper + 1024),
					  (int)(((double)(m_bootInfo->mem_upper + 1024) / 1024) + 0.5),
					  (int)(((double)(m_bootInfo->mem_upper + 1024) / 1024 / 1024) + 0.5));
	PhysicalPageManager::instance().setMemSize((m_bootInfo->mem_upper + 1024) * 1024);
	Multiboot_MemoryMapEntry *mmap;
	output.printf("System memory map:");
	for(mmap = (Multiboot_MemoryMapEntry *)m_bootInfo->mmap_addr ;
		 (unsigned long)mmap < m_bootInfo->mmap_addr + m_bootInfo->mmap_length ;
		 mmap = (Multiboot_MemoryMapEntry *)((unsigned long)mmap + mmap->size + sizeof(mmap->size))) {
		output.printf("\nBase Address: 0x%x%x Length: 0x%x%x Type: %s",
						  (mmap->addr_high), (mmap->addr_low),
						  (mmap->len_high), (mmap->len_low),
						  types[(unsigned)mmap->type]);
		if((unsigned)mmap->type != Multiboot_MemoryEntryType_Avilable) {
			PhysicalPageManager::instance().setUsed(mmap->addr_low, mmap->len_low);
		}
	}
}

void PLMachine::start() {
	PLProcessor::instance().start();
	PhysicalPageManager &phyman = PhysicalPageManager::instance();
	PLKernelDebugConsole &output = PLKernelDebugConsole::instance();
	phyman.init();
    // Create memory management and activate it.
	m_kernelMemManger.createPagingManager();
	m_kernelVirtualManager = new PLVirtualPageManager();
	output.printf("\nInit virtual page management ... ");
	m_kernelVirtualManager->init();
	output.printf("done");
	output.printf("\nAllocating kernel memory ... ");
	m_kernelVirtualManager->createKernelVirtualAddresses();
	output.printf("\ndone");
	output.printf("\nSet virtual page directory ... ");
	m_kernelVirtualManager->setPageDirectory(m_kernelVirtualManager->pageDirectory());
	output.printf("\ndone");
	output.printf("\nSwitch to protected mode ... ");
	PLProcessor::instance().setControlRegister0(PLProcessor::CR0ProtectedMode);
	output.printf("\ndone");
	output.printf("\nStart virtual memory management ... ");
	m_kernelVirtualManager->startPaging();
	output.printf("\ndone");
	output.printf("\nActivate physical memory management ... ");
	phyman.activate();
	m_kernelMemManger.activatePagingManager();
	output.printf("\ndone");
    // Load all base data.
	output.printf("\nLoading C++ runtime ... ");
	crtLoad();
	output.printf("done");
    initModuleInformation();
	PLBootOutput::instance().printf(".");
	initKernelElf();
    m_initrd->loadModules();
	PLProcessor::instance().start1();
	// Nearly all things are loaded and interrupt management was loaded so
	// we can enable interrupts at this time  :)
	PLProcessor::instance().setInterrupts(true);
	PLBootOutput::instance().printf(".");
}

void PLMachine::initModuleInformation() {
    m_moduleHandler = new ModuleHandler();
    m_moduleHandler->init();
    const ModuleInfo *info = m_moduleHandler->getModuleInfo("/boot/initrd");
    m_initrdArchive = new Tar(info->moduleStart(), info->moduleEnd());
    m_initrd = new InitRD(m_initrdArchive);
}

void PLMachine::initKernelElf() {
    m_kernelElf = new KernelElf();
	m_kernelElf->create(m_bootInfo);
}

void PLMachine::addState(LoadStateEnum state) {
	m_state |= state;
}

bool PLMachine::testState(LoadStateEnum state) {
	return m_state.testFlag(state);
}

PLCommandline *PLMachine::kernelCommandline() {
	return m_commandline;
}
